pub(all) enum NodeClass {
  Start;
  Constant(Int);
  Return;
  Add;
  Sub;
  Mul;
  Div;
  NoControl;     // A special node for empty control input
} derive(Eq, Show)

enum NodeFlag {
  Control
  Call
}

pub fn node_flags_of(flag_array: Array[NodeFlag]) -> UInt {
  let mut flags : UInt = 0
  for flag in flag_array {
    let f : UInt = match flag {
      Control => 1
      Call    => 0x10
    }
    flags = flags + f
  }
  flags
}

struct Node {
      _nid     : Int
  mut _inputs  : Array[Node]
  mut _outputs : Array[Node]
      _class   : NodeClass
      _super   : NodeClass?
      _flags   : UInt
} derive(Eq, Show)

let global_id: Ref[Int] = {val: 0}

fn next_id() -> Int {
  global_id.val = global_id.val + 1
  global_id.val
}

let nonCtrlNode: Node = {_nid: next_id(), _inputs: [], _outputs: [], _class: NodeClass::NoControl, _super: None, _flags: node_flags_of([Control])}

fn Node::make(inputs: Array[Node], outputs: Array[Node], nodeClass: NodeClass, superClass: NodeClass?, flags: UInt) -> Node {
  Node::{_nid: next_id(), _inputs: inputs, _outputs: outputs, _class: nodeClass, _super: superClass, _flags: flags}
}

// specific node
struct StartNode(Node)
pub fn StartNode::make() -> Node {
  Node::make([], [], NodeClass::Start, None, node_flags_of([Control, Call]))
}

struct ConstantNode(Node)
pub fn ConstantNode::make(val: Int) -> Node {
  Node::make([], [], NodeClass::Constant(val), None, node_flags_of([]))
}

struct ReturnNode(Node)
pub fn ReturnNode::make(control: Node, expr: Node) -> Node {
  Node::make([control, expr], [], NodeClass::Return, None, node_flags_of([Control]))
}

struct AddNode(Node)
pub fn AddNode::make(lhs: Node, rhs: Node) -> Node {
  Node::make([nonCtrlNode, lhs, rhs], [], NodeClass::Add, None, node_flags_of([]))
}

struct SubNode(Node)
pub fn SubNode::make(lhs: Node, rhs: Node) -> Node {
  Node::make([nonCtrlNode, lhs, rhs], [], NodeClass::Sub, None, node_flags_of([]))
}

struct MulNode(Node)
pub fn MulNode::make(lhs: Node, rhs: Node) -> Node {
  Node::make([nonCtrlNode, lhs, rhs], [], NodeClass::Mul, None, node_flags_of([]))
}

struct DivNode(Node)
pub fn DivNode::make(lhs: Node, rhs: Node) -> Node {
  Node::make([nonCtrlNode, lhs, rhs], [], NodeClass::Div, None, node_flags_of([]))
}

pub fn Node::class(self: Self) -> NodeClass {
  self._class
}

// flag checker
pub fn Node::is_cfg(self: Self) -> Bool {
  let cfg_flag : UInt = node_flags_of([Control])
  (self._flags & cfg_flag) != 0
}

pub fn Node::is_call(self: Self) -> Bool {
  let cfg_flag : UInt = node_flags_of([Call])
  (self._flags & cfg_flag) != 0
}

test "unique id" {
  let s = StartNode::make()
  let c = ConstantNode::make(3)
  let r = ReturnNode::make(s, c)
  if s._nid == c._nid || s._nid == r._nid || c._nid == r._nid {
    fail("nid should be unique")
  }
  assert_eq(s.class(), Start)
  assert_eq(c.class(), Constant(3))
  assert_eq(r.class(), Return)
}

test "property of nodes" {
  let start = StartNode::make()
  let constant = ConstantNode::make(3)
  let ret = ReturnNode::make(start, constant)
  assert_eq(start.class(), Start)
  assert_eq(constant.class(), Constant(3))
  assert_eq(Node::class(ret), Return)

  assert_eq(start.is_cfg(), true)
  assert_eq(start.is_call(), true)
  assert_eq(constant.is_cfg(), false)
  assert_eq(constant.is_call(), false)
  assert_eq(ret.is_cfg(), true)
  assert_eq(ret.is_call(), false)
}
